use std::{
    error::Error,
    ops::{Add, Sub},
    str::FromStr,
};

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
struct Time {
    year: u16,
    month: u16,
}

#[derive(Debug)]
struct InvalidInput;

impl<T: Error> From<T> for InvalidInput {
    fn from(_: T) -> Self {
        InvalidInput
    }
}

impl FromStr for Time {
    type Err = InvalidInput;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut iter = s.split('-');
        let year = iter.next().ok_or(InvalidInput)?.parse()?;
        let month = iter.next().ok_or(InvalidInput)?.parse()?;
        Ok(Time { year, month })
    }
}

impl Time {
    /// Normalize year and month to [`Time`].
    /// e.g. `2025-13` -> `2026-01`, or `2025-00` -> `2024-12`
    fn normalize(year: u16, month: i16) -> Self {
        let year = (year as i16 + month.div_euclid(12)) as u16;
        let month = month.rem_euclid(12) as u16;
        match month {
            0 => Self {
                year: year - 1,
                month: 12,
            },
            _ => Self { year, month },
        }
    }
}

/// [`Time`] difference in months
impl Sub for Time {
    type Output = i16;

    fn sub(self, rhs: Self) -> Self::Output {
        let year_diff = self.year as i16 - rhs.year as i16;
        let month_diff = self.month as i16 - rhs.month as i16;
        year_diff * 12 + month_diff
    }
}

/// Add months to [`Time`]
impl Add<i16> for Time {
    type Output = Self;

    fn add(self, rhs: i16) -> Self::Output {
        let month = self.month as i16 + rhs;
        Self::normalize(self.year, month)
    }
}

/// Subtract months from [`Time`]
impl Sub<i16> for Time {
    type Output = Self;
    fn sub(self, rhs: i16) -> Self::Output {
        let month = self.month as i16 - rhs;
        Self::normalize(self.year, month)
    }
}

enum TP {
    Male,
    Female55,
    Female50,
}

impl FromStr for TP {
    type Err = InvalidInput;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "男职工" => Ok(TP::Male),
            "原法定退休年龄55周岁女职工" => Ok(TP::Female55),
            "原法定退休年龄50周岁女职工" => Ok(TP::Female50),
            _ => Err(InvalidInput),
        }
    }
}

/// See <https://www.gov.cn/yaowen/liebiao/202409/content_6974294.htm> and Appendix 1~3.
impl TP {
    const APPLICATION_YEAR: u16 = 2025;
    const MAX_DELAYED_YEAR: u16 = 2040;

    fn original_retire_age(&self) -> u16 {
        match self {
            TP::Male => 60,
            TP::Female55 => 55,
            TP::Female50 => 50,
        }
    }

    fn max_retire_age(&self) -> u16 {
        match self {
            TP::Male => 63,
            TP::Female55 => 58,
            TP::Female50 => 55,
        }
    }

    fn delay_ratio(&self) -> i16 {
        match self {
            TP::Male => 4,
            TP::Female55 => 4,
            TP::Female50 => 2,
        }
    }
}

pub fn retire_time(time: &str, tp: &str) -> String {
    let birth: Time = time.parse().unwrap();
    let tp: TP = tp.parse().unwrap();

    if birth.year + tp.original_retire_age() < TP::APPLICATION_YEAR {
        // For those who have already reached the retirement age at the time of application,
        // their retirement age will be the same as the original retirement age.
        format!(
            "{}-{:02},{},0",
            birth.year + tp.original_retire_age(),
            birth.month,
            tp.original_retire_age(),
        )
    } else if birth.year + tp.max_retire_age() >= TP::MAX_DELAYED_YEAR {
        // For those who have already reached the maximum retirement age at the time of
        // maximum delayed year, their retirement age will be the same as
        //  the maximum retirement age.
        format!(
            "{}-{:02},{},{}",
            birth.year + tp.max_retire_age(),
            birth.month,
            tp.max_retire_age(),
            (tp.max_retire_age() - tp.original_retire_age()) * 12,
        )
    } else {
        let original_retire = Time {
            year: birth.year + tp.original_retire_age(),
            month: birth.month,
        };

        let months_since_application = original_retire
            - Time {
                year: TP::APPLICATION_YEAR,
                month: 1,
            };

        let delayed_months = months_since_application / tp.delay_ratio() + 1;

        let retire_time = original_retire + delayed_months;
        let retire_age = tp.original_retire_age() as f32 + delayed_months as f32 / 12.0;

        format!(
            "{}-{:02},{:.2},{}",
            retire_time.year, retire_time.month, retire_age, delayed_months,
        )
    }
}
